package opt;

import metadata.MetadataMgr;
import parse.QueryData;
import plan.Plan;
import plan.QueryPlanner;
import query.ProjectPlan;
import tx.Transaction;

import java.util.ArrayList;
import java.util.Collection;

public class HeuristicQueryPlanner implements QueryPlanner {

    private Collection<TablePlanner> tablePlanners = new ArrayList<>();
    private MetadataMgr mdm;

    public HeuristicQueryPlanner(MetadataMgr mdm) {
        this.mdm = mdm;
    }

    /**
     * Creates an optimized left-deep query plan using the following
     * heuristics.
     * H1. Choose the smallest table (considering selection predicates)
     * to be first in the join order.
     * H2. Add the table to the join order which
     * results in the smallest output.
     */
    public Plan createPlan(QueryData data, Transaction tx) {

        // Step 1:  Create a TablePlanner object for each mentioned table
        for (String tblname : data.tables()) {
            TablePlanner tp = new TablePlanner(tblname, data.pred(), tx, mdm);
            tablePlanners.add(tp);
        }

        // Step 2:  Choose the lowest-size plan to begin the join order
        Plan currentPlan = getLowestSelectPlan();

        // Step 3:  Repeatedly add a plan to the join order
        while (!tablePlanners.isEmpty()) {
            Plan p = getLowestJoinPlan(currentPlan);
            if (p != null) {
                currentPlan = p;
            } else  { // no applicable join
                currentPlan = getLowestProductPlan(currentPlan);
            }
        }

        return new ProjectPlan(currentPlan, data.fields());
    }

    private Plan getLowestSelectPlan() {
        TablePlanner besttp = null;
        Plan bestplan = null;
        for (TablePlanner tp : tablePlanners) {
            Plan plan = tp.makeSelectPlan();
            if (bestplan == null || plan.recordsOutput() < bestplan.recordsOutput()) {
                besttp = tp;
                bestplan = plan;
            }
        }
        tablePlanners.remove(besttp);
        return bestplan;
    }

    private Plan getLowestJoinPlan(Plan current) {
        TablePlanner besttp = null;
        Plan bestplan = null;
        for (TablePlanner tp : tablePlanners) {
            Plan plan = tp.makeJoinPlan(current);
            if (plan != null && (bestplan == null || plan.recordsOutput() < bestplan.recordsOutput())) {
                besttp = tp;
                bestplan = plan;
            }
        }
        if (bestplan != null)
            tablePlanners.remove(besttp);
        return bestplan;
    }

    private Plan getLowestProductPlan(Plan current) {
        TablePlanner besttp = null;
        Plan bestplan = null;
        for (TablePlanner tp : tablePlanners) {
            Plan plan = tp.makeProductPlan(current);
            if (bestplan == null || plan.recordsOutput() < bestplan.recordsOutput()) {
                besttp = tp;
                bestplan = plan;
            }
        }
        tablePlanners.remove(besttp);
        return bestplan;
    }
}
